class Vue {
    constructor(options) {
        this.$options = options
        this._data = options.data
        this.initData()
        this.initComputed()
        this.initWatch()
        new Watcher(this, ()=>{
            document.querySelector('#app').innerHTML = `<p>${this.name}</p>`
        }, ()=>{})
    }

    initData() {
        const data = this._data
        const keys = Object.keys(data)
        // 将data里所有key代理到vue实例上
        for(let key of keys) {
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get: function proxyGetter() {
                    return data[key]
                },
                set: function proxySetter(val) {
                    data[key] = val
                }
            })
        }

        observe(data)
    }

    initWatch() {
        const watch = this.$options.watch
        if(watch) {
            const keys = Object.keys(watch)
            for (const key of keys) {
                this.$watch(key, watch[key])
            }
        }
    }

    initComputed() {
        const computed = this.$options.computed
        if(computed) {
            const keys = Object.keys(computed)
            for(let key of keys) {
                const watcher = new Watcher(this, computed[key], () => {}, {
                    lazy: true
                })

                Object.defineProperty(this, key, {
                    enumerable: true,
                    configurable: true,
                    get: function computedGetter(){
                        if(watcher.dirty) {
                            watcher.get()
                            watcher.dirty = false
                        }
                        if(Dep.target) {
                            for(const dep of watcher.deps) {
                                dep.depend()
                            } 
                        }
                        
                        return watcher.value
                    },
                    set: function computedSetter() {
                        console.warn('请不要给计算属性赋值')
                    }
                })
            }
        }
    }

    $watch(exp, cb) {
        new Watcher(this, exp, cb)
    }

    // $set新增code
    $set(target, key, value) {
        // 响应式化新key
        defineReactive(target, key, value)
        // 通知新key的所有依赖更新
        target.__ob__.dep.notify()
    }
}

function isObject(obj) {
    return obj !== null && typeof obj === 'object'
}
// 判断当前观察data类型，如果是对象创建Observe对象进行reactive
function observe(data) {
    if(!isObject(data)) {
        return
    }
    // $set新增code
    if(data.__ob__) {
        return data.__ob__
    }
    // new Observer(data) 这里需要return！！！
    return new Observer(data)
}
class Observer {
    constructor(data) {
        // array add
        if(Array.isArray(data)) {
            data.__proto__ = arrayMethods
            this.observeArray(data)
        } else {
            this.walk(data)
        }

        this.dep = new Dep()
        Object.defineProperty(data, '__ob__', {
            value: this,
            enumerable:false,
            configurable:true,
            writable:true
        })
    }
    walk(data) {
        const keys = Object.keys(data)
        for(let key of keys) {
            defineReactive(data, key, data[key])
        }
    }
    // array add
    observeArray(items){
        for (const item of items) {
            observe(item)
        }
    }
}

function defineReactive(obj, key, value) {
    let childOb = observe(value) // $set新增code赋值
    let dep = new Dep()
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter(){
            dep.depend()
            // $set新增code
            if(childOb) {
                childOb.dep.depend()
            }
            return value
        },
        set: function reactiveSetter(val) {
            if(val === value) {
                return
            }
            dep.notify()
            value = val
        }
    })
}

const targetStack = []
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(watcher) {
        this.subs.push(watcher)
    }
    depend(){
        if(Dep.target) {
            // this.addSub(Dep.target)
            Dep.target.addDep(this)
        }
    }
    notify(){
        this.subs.forEach((watcher) => {
            watcher.update()
        })
    }
}
let watcherId = 0
let watcherQueue = []
class Watcher { // 回调的抽象
    constructor(vm, exp, cb, options = {}) {
        this.dirty = this.lazy = !!options.lazy
        this.vm = vm
        this.exp = exp
        this.cb = cb
        this.id = ++watcherId // 回调的唯一标识
        this.deps = []
        if(!this.lazy) this.get()
    }
    addDep(dep) {
        if(this.deps.indexOf(dep) !== -1) return
        this.deps.push(dep)
        dep.addSub(this)
    }
    get() {
        targetStack.push(this)
        Dep.target = this
        if(typeof this.exp === 'function') {
            this.value = this.exp.call(this.vm)
        } else {
            this.value = this.vm[this.exp]
        }
        targetStack.pop()
        if(targetStack.length > 0) {
            Dep.target = targetStack[targetStack.length - 1]
        } else {
            Dep.target = null
        }
    }
    update() {
        if(this.lazy) {
            this.dirty = true
        } else {
            this.run()
        }
    }
    run() {
        if(watcherQueue.indexOf(this.id) !== -1) {
            return // 已经存在队列中，就不用执行了
        }
        watcherQueue.push(this.id)
        Promise.resolve().then(() => {
            this.get()
            this.cb.call(this.vm, this.value)
            let index = watcherQueue.indexOf(this.id)
            watcherQueue.splice(index, 1)
        })
    }
}

const arrayMethods = Object.create(Array.prototype)
// const arrayMethods = {}
// arrayMethods.__proto__ = Array.prototype
const methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]
methodsToPatch.forEach(method => {
    arrayMethods[method] = function(...args) {
        const result = Array.prototype[method].apply(this, args)
        const ob = this.__ob__
        let inserted
        switch (method) {
            case 'push':
            case 'unshift':
                inserted = args
                break
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if (inserted) ob.observeArray(inserted)
        ob.dep.notify()
        return result
    }
})

// let vm = new Vue({
//     data:{
//       a: 1,
//       b: 2
//     },
//     watch:{
//       sum(val) { //2号watcher
//         console.log('sum:' + val);
//       }
//     },
//     computed:{
//       sum() { //1号watcher
//         console.log('sum计算')
//         return this.a + this.b
//       }
//     }
// })

